#include "sc/config.h"
#include "sc/interface.h"
#include "sc/xml.h"

#include "libxml/xmlmemory.h"
#include "libxml/relaxng.h"
#include "libxml/xpathInternals.h"

#include <sstream>

using namespace sc;

/**
* Config constructor
*/
Config::Config(void)
: m_rngDoc(NULL)
, m_xpathCtx(NULL)
{
}

/**
* Config destructor
*/
Config::~Config(void)
{
	// TODO : Free the m_rngDoc
}

/**
* Tells if a file name is a valid Relax NG file
*
* @param sFileRNG
*
* @return true if file is a valid RNG file
*/
bool Config::IsRngValid(const std::string& sFileRNG)
{
    bool bValid = false;

    xmlRelaxNGParserCtxtPtr rngCtx = xmlRelaxNGNewParserCtxt(sFileRNG.c_str());
    xmlRelaxNGPtr schema = xmlRelaxNGParse(rngCtx);
    xmlRelaxNGFreeParserCtxt(rngCtx);
    if (schema)
    {
        xmlRelaxNGFree(schema);
        bValid = true;
    }
    return bValid;
}

/**
* Load a config file, ie a RNG file which describ the setup
*
* @param sFile  file name
*
* @return loading error
*/
int Config::LoadRng(const std::string& sFile)
{
    int error = SCE_NONE;

    // Validate the RNG file
    if (!IsRngValid(sFile))
        return SCE_SHEMA_INVALID;

    // Load the RNG document
    m_rngDoc   = xmlReadFile(sFile.c_str(), NULL, 0);
    m_xpathCtx = xmlXPathNewContext(m_rngDoc);
    if (m_xpathCtx == NULL)
        return SCE_XPATH_CTX_INVALID;

    xmlXPathRegisterNs(m_xpathCtx, xml::rng::NS, xml::rng::NS_VALUE);

    // Now, we can fill the map of element
    m_mapElement.clear();

    // Get the root name
    std::string sRoot;
    error = GetRoot(sRoot);
    if (!error)
    {
        if (sRoot.empty())
        {
            error = SCE_SHEMA_INVALID;
        }
        else
        {
            // insert the root element
            Element root;
            root.sName  = sRoot;
            root.type   = Element::UNIQUE;

            TMapElement::value_type val = std::make_pair(sRoot, root);
            std::pair<TMapElement::iterator, bool> res = m_mapElement.insert(val);
            if (res.second)
            {
                // Get the childs of the root element
                error = GetChildren(res.first);
            }
            else
            {
                error = SCE_UNKNOWN;
            }
        }
    }
    return error;
}

/**
* Get the element root name
*
* @param sRoot [out] root name
*
* @return error
*/
int Config::GetRoot(std::string& sRoot) const
{
    int error = SCE_NONE;

    if (m_rngDoc == NULL)
        return SCE_RNG_NOT_LOADED;

    if (m_xpathCtx == NULL)
        return SCE_XPATH_CTX_INVALID;

    // Search the start
    xmlXPathObjectPtr result = xmlXPathEvalExpression((const xmlChar*)"//sc:start/sc:element", m_xpathCtx);
    if (result == NULL)
        return SCE_XPATH_INVALID_EXPRESSION;

    if (xmlXPathNodeSetIsEmpty(result->nodesetval))
    {
        xmlXPathFreeObject(result);
        return SCE_XPATH_NO_RESULT;
    }

    // Get the root name
    xmlNodeSetPtr nodeset = result->nodesetval;
    if (nodeset->nodeNr == 1)
    {
        const xmlChar* sName = xmlGetProp(nodeset->nodeTab[0], xml::rng::_name);
        if (sName == NULL)
        {
            error = SCE_SHEMA_INVALID;
        }
        else
        {
            sRoot = (const char*) sName;
        }
	    xmlXPathFreeObject(result);
    }
    else
    {
        error = SCE_SHEMA_INVALID;
    }
    return error;
}

/**
* Get the children of an element
*
* @param iterRoot  iterator to the root element of which we are looking for the children
*
* @return error
*/
int Config::GetChildren(TMapElement::iterator iterRoot)
{
    int error = SCE_NONE;

    if (m_rngDoc == NULL)
        return SCE_RNG_NOT_LOADED;

    if (m_xpathCtx == NULL)
        return SCE_XPATH_CTX_INVALID;

    // Create the xPath request
    std::string sName =  (*iterRoot).first;
    std::string xPath = "//sc:element[@name='" +  sName + "']";

    // execute it
    xmlXPathObjectPtr result = xmlXPathEvalExpression((const xmlChar*)xPath.c_str(), m_xpathCtx);
    if (result == NULL)
        return SCE_XPATH_INVALID_EXPRESSION;

    if (xmlXPathNodeSetIsEmpty(result->nodesetval))
    {
        xmlXPathFreeObject(result);
        return SCE_XPATH_NO_RESULT;
    }

    // Get the element
    xmlNodeSetPtr nodeset = result->nodesetval;
    if (nodeset->nodeNr == 1)
    {
        // Now we got the element, we can find the children
		error = GetChildren(iterRoot, nodeset->nodeTab[0], sc::Config::Element::UNIQUE);
		xmlXPathFreeObject(result);
    }
    else
    {
        error = SCE_SHEMA_INVALID;
    }
    return error;
}

/**
* Get the children of an element
*
* @param iterRoot  iterator on the element
* @param node      xml node of the element
* @param type      type of the element
*
* @return error
*/
int Config::GetChildren(TMapElement::iterator iterRoot, xmlNodePtr node, Element::Type type)
{
	int error = SCE_NONE;

    xmlNodePtr childNode = node->children;
    while (childNode && !error)
    {
        if (childNode->type == XML_ELEMENT_NODE)
        {
            if (!xmlStrcmp(childNode->name, xml::rng::_element))
            {
                // Here is the element
				error = GetElement(iterRoot, childNode, type);
            }
			else if (!xmlStrcmp(childNode->name, xml::rng::_ref))
			{
				// It is a reference, jump to it
				const xmlChar* sName = xmlGetProp(childNode, xml::rng::_name);
				if (sName)
				{
					std::string xPath = "//sc:define[@name='";
					xPath += (const char*)sName;
					xPath += "']";

					xmlXPathObjectPtr result = xmlXPathEvalExpression((const xmlChar*)xPath.c_str(), m_xpathCtx);
					if (result)
					{
						xmlNodeSetPtr nodeset = result->nodesetval;
						if (nodeset->nodeNr == 1)
						{
							// We found the reference, we can find the children
							xmlNodePtr refNode = nodeset->nodeTab[0];
							error = GetChildren(iterRoot, refNode, type);
						}
					}
				}
			}
			else if (!xmlStrcmp(childNode->name, xml::rng::_zeroOrMore))
			{
				// It is a multiple (0 or more) element
				error = GetChildren(iterRoot, childNode, sc::Config::Element::MULTIPLE);
			}
			else if (!xmlStrcmp(childNode->name, xml::rng::_optional))
			{
				// It is an optional (0 or 1) element
				error = GetChildren(iterRoot, childNode, sc::Config::Element::OPTION);
			}
			else if (!xmlStrcmp(childNode->name, xml::rng::_attribute))
			{
				// It is an attribute of the parent
				GetAttribute((*iterRoot).second, childNode);
			}
        }
		childNode = childNode->next;
    }
	return error;
}

/**
* Get the Element
*
* @param parent  iterator on the parent of the element
* @param node    xml node of the element
* @param type    type of the element
*
* @return error
*/
int Config::GetElement(TMapElement::iterator parent, xmlNodePtr node, Element::Type type)
{
	int error = SCE_SHEMA_INVALID;

    xmlChar* sName = xmlGetProp(node, xml::rng::_name);
    if (sName != NULL)
    {
		Element elem;
		elem.sName  = (const char*) sName;
		elem.type   = type;

		// Insert the child
		TMapElement::value_type val = std::make_pair(elem.sName, elem);
		std::pair<TMapElement::iterator, bool> res = m_mapElement.insert(val);
		TMapElement::iterator iterElem = res.first;
		if (res.second == false)
		{
			// The element already exist
			iterElem = m_mapElement.find(elem.sName);
		}

		// Add the child to the parent
		(*parent).second.children.push_back(iterElem);

		// Get the children
		error = GetChildren(iterElem);
	}
	return error;
}

/**
* Get the attribute corresponding to the xml node
*
* @param rElement  Element which corresponde to the current attribute
* @param node      xml node of the attribute
*
* @return error
*/
int Config::GetAttribute(Element& rElement, xmlNodePtr node)
{
	int error = SCE_SHEMA_INVALID;

	xmlChar* sName = xmlGetProp(node, xml::rng::_name);
    if (sName != NULL)
    {
		Attribute att;
		att.mask  = Attribute::STRING;
		att.sName = (const char*) sName;

		rElement.attributes.push_back(att);

		// Get the iterator on the created attribute
		Attribute& rAttribute = rElement.attributes.back();

		error = SCE_NONE;

		// Look for the available value(s) for this attribute
		xmlNodePtr childNode = node->children;
		while (childNode && !error)
		{
			if (childNode->type == XML_ELEMENT_NODE)
			{
				if (!xmlStrcmp(childNode->name, xml::rng::_data))
				{
					error = GetAttributeData(rAttribute, childNode);
				}
				else if (!xmlStrcmp(childNode->name, xml::rng::_choice))
				{
					error = GetAttributeChoice(rAttribute, childNode);
				}
			}
			childNode = childNode->next;
		}

	}
	return error;
}

/**
* Get the data of attribute corresponding to the xml node
*
* @param rAttribute  attribute
* @param node        xml node of the attribute's data
*
* @return error
*/
int Config::GetAttributeData(Attribute& rAttribute, xmlNodePtr node)
{
	int error = SCE_SHEMA_INVALID;

	const xmlChar* sType = xmlGetProp(node, xml::rng::_type);
	if (sType)
	{
		if (!xmlStrcmp(sType, xml::rng::_int) || !xmlStrcmp(sType, xml::rng::_integer))
		{
			rAttribute.mask = Attribute::INT;
		}
		else if (!xmlStrcmp(sType, xml::rng::_float))
		{
			rAttribute.mask = Attribute::DOUBLE;
		}
	}
	return error;
}

/**
* Get the choices of attribute corresponding to the xml node
*
* @param rAttribute  attribute
* @param node        xml node of the attribute's data
*
* @return error
*/
int Config::GetAttributeChoice(Attribute& rAttribute, xmlNodePtr node)
{
	int error = SCE_NONE;

	rAttribute.mask = Attribute::CHOICE;

	xmlNodePtr childNode = node->children;
	while (childNode && !error)
	{
		if (childNode->type == XML_ELEMENT_NODE)
		{
			if (!xmlStrcmp(childNode->name, xml::rng::_value) && childNode->children)
			{
				const xmlChar* sValue = childNode->children->content;

				rAttribute.lstChoice.push_back( (const char*) sValue );
			}
		}
		childNode = childNode->next;
	}
	return error;
}

/**
* Remove all information about the Config
*/
void Config::Clear(void)
{
    m_sFilename.clear();
    if (m_rngDoc)
    {
        xmlFreeDoc(m_rngDoc);
        m_rngDoc = NULL;
    }
    if (m_xpathCtx)
    {
        xmlXPathFreeContext(m_xpathCtx);
        m_xpathCtx = NULL;
    }
}
